<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JavaScript 面向对象编程（一）：封装</title>
<!-- JS代码  -->
<script type="text/javaScript">

/**
 * 一、最简单的封装
 */
// 生成实例对象的原始模式
var cat = {
		name : '',
		color : ''
};


//生成两个实例对象。
var cat1 = {};//创建空对象
cat1.name = '大猫';
cat1.color = '黄色';
var cat2 = {};
cat2.name = '小猫';
cat2.color = '黑色';
console.dir(cat1);
console.log("%o", cat2);

/*
 * 好了，以上就是最简单的封装了，把两个属性封装在一个对象里面。
 * 但是，这样的写法有两个缺点，一是如果多生成几个实例，写起来就非常麻烦；
 * 二是实例与原型之间，没有任何办法可以看出有什么联系。
 */

 /**
 *  二、原始模式的改进
 */
 //原始模式的改进
 function Dog(name, color){
	return {
		name : name,
		color : color
	};
 }

var dog1 = Dog("黑贝","黑色");
var dog2 = Dog("泰迪","棕色");
console.dir(dog1);
console.log("%o", dog2);
/*
 * 以上这种方法的问题依然是，dog1和dog2之间没有内在的联系，不能反映出它们是同一个原型对象的实例。
 */
 
 
 /*
 * 三、构造函数模式
 * 为了解决从原型对象生成实例的问题，Javascript提供了一个构造函数（Constructor）模式。
 * 所谓"构造函数"，其实就是一个普通函数，但是内部使用了this变量。
 * 对构造函数使用new运算符，就能生成实例，并且this变量会绑定在实例对象上。 
 *  3.1  constructor 构造方法
 *  3.2   instanceof 运算符   实例名A instanceof 类名B  用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
 *  说白了就是检测，实例A是不是 类名B 的孩子或者子类(是不是通过new构造出来的实例对象)。
 */
 
 function Animal(name,color){
	  this.name = name;
	  this.color = color;
 }

 var obj1 = new Animal("大毛","白色");
 var obj2 = new Animal("二毛","黄色");
 console.dir(obj1);
 console.dir(obj2);
 //输出属性看看
 console.log(obj1.name);
 console.log(obj1.color);
 //这时obj1和obj2会自动含有一个constructor属性，指向它们的构造函数。
  console.info('constructor属性，指向它们的构造函数');
 console.log(obj1.constructor === Animal);
 console.log(obj2.constructor == Animal);
 /*
 * 简单的理解JS中的 == 与 === (其实是很复杂的)
 *	 1.==，理解为简单的值比较，如 a=5，b=5那么 a==b 就为true,因为赋予的值都是5，所以返回true.
 *  2.===，理解为内存地址的比较，如 String a ="你好"，String b="你好"；那么 a === b就为false;
 *  因为String a 与 String b 的内存地址不一样，只是赋予的字符串"你好"一样。所以返回false. 
 */
 
 //Javascript还提供了一个instanceof运算符，验证原型对象与实例对象之间的关系。
 console.info('instanceof运算符，验证原型对象与实例对象之间的关系');
 console.log(obj1 instanceof Animal);
 console.log(obj2 instanceof Animal);
 
 
/**
 * 四、构造函数模式的问题
 * 构造函数方法很好用，但是存在一个浪费内存的问题。
 * 请看，我们现在为Animal对象添加一个不变的属性type（种类），
 * 再添加一个方法eat（吃）。那么，原型对象Animal就变成了下面这样：
 */
 
 function Animal2(name,color){
	this.name = name;
	this.color = color;
	this.type = "动物类型";//不变的属性值
	this.eat = function(){console.log("正在吃饭!");};//不变的方法
}

var aObj1 = new Animal2("大动物","灰色");
var aObj2 = new Animal2("小动物","黑白配");
console.log(aObj1.type);
aObj1.eat();
console.log(aObj2.type);
aObj2.eat();

/*
 * 以上内容在表面上好像没什么问题，但是实际上这样做，有一个很大的弊端。
 * 那就是对于每一个实例对象，type属性和eat()方法都是一模一样的内容，
 * 每一次生成一个实例，都必须为重复的内容，多占用一些内存。这样既不环保，也缺乏效率。
 */
 console.log("查看 aObj1.eat == aObj2.eat是否是同一地址，是否占用同一内存地址，是否相等？");
 console.log(aObj1.eat == aObj2.eat);//false
 
 //能不能让type属性和eat()方法在内存中只生成一次，然后所有实例都指向那个内存地址呢？回答是可以的。看下面的内容
 
 /*
 * 五、 Prototype模式 
 *
 * 解释：Javascript规定，每一个构造函数都有一个prototype属性，指向另一个对象。
 *            这个对象的所有属性和方法，都会被构造函数的实例继承。
 *
 * 这意味着，我们可以把那些不变的属性和方法，直接定义在prototype对象上，让这些内容占用同一块内存地址。
 */
 
 function Animal3(name,color){
	 this.name = name;
	 this.color = color;
 }
 Animal3.prototype.type="动物类型3";
 Animal3.prototype.eat = function(){console.log("动物类型3吃饭中！");};
 
 var protoObj1 = new Animal3("动物1","红色");
 var protoObj2 = new Animal3("动物2","绿色");
 console.log(protoObj1.type);
 protoObj1.eat();
 console.log(protoObj2.type);
 protoObj2.eat();
 
 //这时所有实例的type属性和eat()方法，其实都是同一个内存地址，指向prototype对象，因此就提高了运行效率。
 //无论是实例protoObj1还是实例protoObj2里面的type属性和eat()方法，都是指向同一个对象prototype对象中的type属性和eat()方法。
 //占用的是同一块内存地址。所以相等！
 console.log("查看 protoObj1.eat == protoObj2.eat是否是同一地址，是否占用同一内存地址，是否相等？");
 console.log(protoObj1.eat == protoObj2.eat);//true
 
 /*
 * 六、 Prototype模式的验证方法 
 * 为了配合prototype属性，Javascript定义了一些辅助方法，帮助我们使用它。
 * 6.1 isPrototypeOf()
 * 6.2 hasOwnProperty() 
 * 6.3 in运算符
 */
 
 /*
 * 6.1 isPrototypeOf() 
 * 这个方法是用来检查其原型链的对象是否存在于指定对象实例中，是则返回true，否则返回false。
 */
 console.log("Animal3类中的prototype原型链对象是否存在于与protoObj1对象实例中");
 console.log(Animal3.prototype.isPrototypeOf(protoObj1));//true
 console.log("Animal3类中的prototype原型链对象是否存在于与protoObj2对象实例中");
 console.log(Animal3.prototype.isPrototypeOf(protoObj2));//true

 /*
 * 6.2 hasOwnProperty() 
 * 每个实例对象都有一个hasOwnProperty()方法
 * 解释1：是用来判断一个对象是否有你给出名称的属性或对象，自身包含该属性则返回true，不包含则返回false。
 *              不过需要注意的是，此方法无法检查该对象的原型链中是否具有该属性，该属性必须是对象本身的一个成员。
 * 解释2：也可以理解为用来判断某一个属性到底是本实例自身的属性(返回true)，还是继承自prototype对象的属性(返回false)。
 *
 */
 console.log("hasOwnProperty()  是否有你给出名称的属性或对象");
 console.log(protoObj1.hasOwnProperty("name"));//true
 console.log(protoObj1.hasOwnProperty("type"));//false
 
 /*
 * 6.3 in运算符
 * ① in运算符可以用来判断，某个实例是否含有某个属性，不管是不是自身属性，还是原型链中的属性。
 * ② in运算符还可以用来遍历某个对象的所有属性。
 */
 console.log(" ① in运算符 某个实例是否含有某个属性：");
 console.log("name" in protoObj1);
 console.log("type" in protoObj2);
 
 console.log(" ② in运算符遍历某个对象的所有属性：");
 for( var property in protoObj1 ){//将实例protoObj1中的属性名 一个一个的赋值给 property变量
	 console.log("protoObj1['"+property+"']  =" + protoObj1[property]);
 }
</script>
</head>
<body>
<h3>JavaScript 面向对象编程（一）：封装</h3>
<h1><a href="http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html">参考地址链接.</a></h1>

请打开控制台，查看console对象的输出情况！！！
</body>
</html>